#ifndef __ETLSDK_H__
#define __ETLSDK_H__

#if LINUX_SYSTEM
#include <xInc/xDef.h>
#include <xInc/xSDK.h>
#else
#include <wInc/wDef.h>
#include <wInc/wSDK.h>
#endif

inline UINT64 etlGetCPUTickCount()
{
#if LINUX_SYSTEM
	return xGetCPUTickCount();
#else
	return wGetCPUTickCount();
#endif
};

inline DWORD etlGetTickCount()
{
#if LINUX_SYSTEM
	return xGetTickCount();
#else
	return wGetTickCount();
#endif
}

inline int etlWideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cchMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
{
#if LINUX_SYSTEM
	return xWideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cchMultiByte, lpDefaultChar, lpUsedDefaultChar);
#else
	return wWideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cchMultiByte, lpDefaultChar, lpUsedDefaultChar);
#endif
}

inline int etlMultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cchMultiByte, LPWSTR lpWideCharStr, int cchWideChar)
{
#if LINUX_SYSTEM
	return xMultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cchMultiByte, lpWideCharStr, cchWideChar);
#else
	return wMultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cchMultiByte, lpWideCharStr, cchWideChar);
#endif
}

inline HMODULE etlModuleFromAddress(const void* pv)
{
#if LINUX_SYSTEM
	return xModuleFromAddress(pv);
#else
	return wModuleFromAddress(pv);
#endif
}

inline int etlMakeToStandardPath(LPTSTR lpszPath)
{
#if LINUX_SYSTEM
	return xMakeToStandardPath(lpszPath);
#else
	return wMakeToStandardPath(lpszPath);
#endif
}

inline void etlGetLocalTime(SYSTEMTIME* lpTime)
{
	chASSERT(lpTime != NULL);
#if LINUX_SYSTEM
	xGetLocalTime(lpTime);
#else
	wGetLocalTime(lpTime);
#endif
}

inline static int etlGetFileNamePos(LPTSTR lpszPath)
{
	int iFilePos = 0;
	int i = 0;
	for(i = 0; lpszPath[i] != '\0'; i++)
	{
		if(lpszPath[i] == '\\' || lpszPath[i] == '/')
			iFilePos = i;
	}
	return iFilePos;
}

inline void etlPathStripPath(LPTSTR lpszPath)
{// remove path left filename
	int iFilePos = etlGetFileNamePos(lpszPath);
	if(iFilePos != 0)
	{
		iFilePos ++;
		int i = 0;
		for(i = 0; lpszPath[iFilePos] != '\0';)
		{
			lpszPath[i++] = lpszPath[iFilePos++];
		}
		lpszPath[i++] = lpszPath[iFilePos++];
	}
}

inline void etlPathRemoveFileSpec(LPTSTR lpszPath)
{// remove filename left path
	int iFilePos = etlGetFileNamePos(lpszPath);
	lpszPath[iFilePos] = '\0';
}

inline void etlPathAppend(LPTSTR pszPath, LPCTSTR pszMore)
{
	LPTSTR lpszEnd = pszPath + _tcslen(pszPath) - 1;
	while(lpszEnd > pszPath && (*lpszEnd == '\\' || *lpszEnd == '/'))
		*(lpszEnd --) = '\0';
	*(++lpszEnd) = '\\';
	lpszEnd ++;

	while(*pszMore != '\0' && (*pszMore == '\\' || *pszMore == '/'))
		pszMore ++;
		
	while(*pszMore != '\0')
		*(lpszEnd++) = *(pszMore++);
	*(lpszEnd++) = *(pszMore++);
}


inline BOOL etlPathFileExists(LPCTSTR lpszFileName)
{
#if LINUX_SYSTEM
	return xPathFileExists(lpszFileName);
#else
	return wPathFileExists(lpszFileName);
#endif
}

inline BYTE etlReversalByte(BYTE bVal)
{
	BYTE bRe = 0;
	if(bVal & 0x01)		bRe |= 0x80;
	if(bVal & 0x02)		bRe |= 0x40;
	if(bVal & 0x04)		bRe |= 0x20;
	if(bVal & 0x08)		bRe |= 0x10;
	if(bVal & 0x10)		bRe |= 0x08;
	if(bVal & 0x20)		bRe |= 0x04;
	if(bVal & 0x40)		bRe |= 0x02;
	if(bVal & 0x80)		bRe |= 0x01;
	return bRe;
}

template<class TYPE>
inline int etlPreFormatString(const TYPE* lpszFormat, va_list argList)
{
	typedef const TYPE* LPCTYPE;

	chASSERT(lpszFormat != NULL);
	enum _FormatModifiers
	{
		FORCE_ANSI =	0x10000,
		FORCE_UNICODE =	0x20000,
		FORCE_INT64 =	0x40000
	};

	// make a guess at the maximum length of the resulting string
	int nMaxLen = 0;
	for (LPCTYPE lpsz = lpszFormat; *lpsz != '\0'; lpsz = chstrinc(lpsz))
	{
		// handle '%' character, but watch out for '%%'
		if (*lpsz != '%' || *(lpsz = chstrinc(lpsz)) == '%')
		{
			nMaxLen += chstrlen(lpsz);
			continue;
		}
		
		int nItemLen = 0;
		
		// handle '%' character with format
		int nWidth = 0;
		for (; *lpsz != '\0'; lpsz = chstrinc(lpsz))
		{
			// check for valid flags
			if (*lpsz == '#')
				nMaxLen += 2;   // for '0x'
			else if (*lpsz == '*')
				nWidth = va_arg(argList, int);
			else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' ||
				*lpsz == ' ')
				;
			else // hit non-flag character
				break;
		}
		// get width and skip it
		if (nWidth == 0)
		{
			// width indicated by
			nWidth = chatoi(lpsz);
			for (; *lpsz != '\0' && chisdigit(*lpsz); lpsz = chstrinc(lpsz))
				;
		}
		chASSERT(nWidth >= 0);
		
		int nPrecision = 0;
		if (*lpsz == '.')
		{
			// skip past '.' separator (width.precision)
			lpsz = chstrinc(lpsz);
			
			// get precision and skip it
			if (*lpsz == '*')
			{
				nPrecision = va_arg(argList, int);
				lpsz = chstrinc(lpsz);
			}
			else
			{
				nPrecision = chatoi(lpsz);
				for (; *lpsz != '\0' && chisdigit(*lpsz); lpsz = chstrinc(lpsz))
					;
			}
			chASSERT(nPrecision >= 0);
		}
		
		// should be on type modifier or specifier
		int nModifier = 0;
		if (chstrlen(lpsz) >= 3 && lpsz[0] == 'I' && lpsz[1] == '6' && lpsz[2] == '4')
		{
			lpsz += 3;
			nModifier = FORCE_INT64;
#if !defined(_X86_) && !defined(_ALPHA_)
			// __int64 is only available on X86 and ALPHA platforms
			chASSERT(FALSE);
#endif
		}
		else
		{
			switch (*lpsz)
			{
				// modifiers that affect size
			case 'h':
				nModifier = FORCE_ANSI;
				lpsz = chstrinc(lpsz);
				break;
			case 'l':
				nModifier = FORCE_UNICODE;
				lpsz = chstrinc(lpsz);
				break;
				
				// modifiers that do not affect size
			case 'F':
			case 'N':
			case 'L':
				lpsz = chstrinc(lpsz);
				break;
			}
		}
		
		// now should be on specifier
		switch (*lpsz | nModifier)
		{
			// single characters
		case 'c':
		case 'C':
			nItemLen = 2;
			va_arg(argList, TYPE);
			break;
		case 'c'|FORCE_ANSI:
		case 'C'|FORCE_ANSI:
			nItemLen = 2;
			va_arg(argList, CHAR);
			break;
		case 'c'|FORCE_UNICODE:
		case 'C'|FORCE_UNICODE:
			nItemLen = 2;
			va_arg(argList, WCHAR);
			break;
			
			// strings
		case 's':
			{
				LPCTYPE pstrNextArg = va_arg(argList, LPCTYPE);
				if (pstrNextArg == NULL)
					nItemLen = 6;  // "(null)"
				else
				{
					nItemLen = chstrlen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
			}
			break;
			
		case 'S':
			{
#ifndef _UNICODE
				LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6;  // "(null)"
				else
				{
					nItemLen = wcslen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
#else
				LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6; // "(null)"
				else
				{
					nItemLen = strlen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
#endif
			}
			break;
			
		case 's'|FORCE_ANSI:
		case 'S'|FORCE_ANSI:
			{
				LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6; // "(null)"
				else
				{
					nItemLen = strlen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
			}
			break;
			
		case 's'|FORCE_UNICODE:
		case 'S'|FORCE_UNICODE:
			{
				LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
				if (pstrNextArg == NULL)
					nItemLen = 6; // "(null)"
				else
				{
					nItemLen = wcslen(pstrNextArg);
					nItemLen = chMAX(1, nItemLen);
				}
			}
			break;
		}
		
		// adjust nItemLen for strings
		if (nItemLen != 0)
		{
			if (nPrecision != 0)
				nItemLen = chMIN(nItemLen, nPrecision);
			nItemLen = chMAX(nItemLen, nWidth);
		}
		else
		{
			switch (*lpsz)
			{
				// integers
			case 'd':
			case 'i':
			case 'u':
			case 'x':
			case 'X':
			case 'o':
				if (nModifier & FORCE_INT64)
					va_arg(argList, INT64);
				else
					va_arg(argList, int);
				nItemLen = 32;
				nItemLen = chMAX(nItemLen, nWidth+nPrecision);
				break;
				
			case 'e':
			case 'g':
			case 'G':
				va_arg(argList, DOUBLE);
				nItemLen = 128;
				nItemLen = chMAX(nItemLen, nWidth+nPrecision);
				break;
				
			case 'f':
				va_arg(argList, DOUBLE);
				nItemLen = 128; // width isn't truncated
				// 312 == strlen("-1+(309 zeroes).")
				// 309 zeroes == max precision of a double
				nItemLen = chMAX(nItemLen, 312+nPrecision);
				break;
				
			case 'p':
				va_arg(argList, void*);
				nItemLen = 32;
				nItemLen = chMAX(nItemLen, nWidth+nPrecision);
				break;
				
				// no output
			case 'n':
				va_arg(argList, int*);
				break;
				
			default:
				chASSERT(FALSE);  // unknown formatting option
			}
		}
		// adjust nMaxLen for output nItemLen
		nMaxLen += nItemLen;
	}
	return nMaxLen;
}

inline double etlFloor(double x, double mod)
{
	chASSERT(mod > 0);
	double xZeroSide = x - fmod(x, mod);
	if(x >= xZeroSide)
		return xZeroSide;
	else
		return xZeroSide - mod;
}

inline int etlFloor(double x)
{
	int iZeroSide = (int)x;
	if(x >= iZeroSide)
		return iZeroSide;
	else
		return iZeroSide - 1;
}

inline double etlCeil(double x, double mod)
{
	chASSERT(mod > 0);
	double xZeroSide = x - fmod(x, mod);
	if(x > xZeroSide)
		return xZeroSide + mod;
	else
		return xZeroSide;
}

inline double etlCeil(double x)
{
	int iZeroSide = (int)x;
	if(x > iZeroSide)
		return iZeroSide + 1;
	else
		return iZeroSide;
}

inline double etlNear(double x, double mod, double dRange)
{
	chASSERT(mod > 0 && dRange > 0 && dRange < mod);
	double xFloor = etlFloor(x, mod);
	if(x - xFloor >= dRange)
		return xFloor + mod;
	else
		return xFloor;
}

inline int etlRound(double x, double dRange = 0.5)
{
	chASSERT(dRange > 0.0 && dRange < 1.0);
	int iFloor = etlFloor(x);
	if(x - iFloor >= dRange)
		return iFloor + 1;
	else
		return iFloor;
}
#endif //__ETLSDK_H__
